﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows.Input;
using System.Windows.Threading;

namespace SampleCode.VirtualFS.ViewModel
{
    /// <summary>
    /// A folder View Model
    /// </summary>
    public class FsFolderViewModel : FsViewModelBase
    {
        /// <summary>
        /// A reference to Dispatcher. Used for thread-safe ObservableCollections
        /// </summary>
        private readonly Dispatcher _dispatcher;

        /// <summary>
        /// Folder info entity
        /// </summary>
        private readonly FsFolderInfo _folderInfo;

        /// <summary>
        /// The disk
        /// </summary>
        private readonly FsDisk _disk;

        /// <summary>
        /// Was Create Child Folder command invoked?
        /// </summary>
        private bool _isCreatingChildFolder;

        /// <summary>
        /// Was Create File command invoked?
        /// </summary>
        private bool _isCreatingFile;

        /// <summary>
        /// Is search mode on?
        /// </summary>
        private bool _inSearchMode;

        /// <summary>
        /// A ctor
        /// </summary>
        /// <param name="dispatcher"></param>
        /// <param name="disk"></param>
        /// <param name="folderInfo"></param>
        public FsFolderViewModel(Dispatcher dispatcher, FsDisk disk, FsFolderInfo folderInfo)
            : this(dispatcher, disk, folderInfo, false)
        {
        }

        /// <summary>
        /// A ctor, which is used, when a folder is created - it turns on edit mode
        /// </summary>
        /// <param name="dispatcher"></param>
        /// <param name="disk"></param>
        /// <param name="folderInfo"></param>
        /// <param name="inEditMode"></param>
        public FsFolderViewModel(Dispatcher dispatcher, FsDisk disk, FsFolderInfo folderInfo, bool inEditMode) 
        {
            SearchPattern = "*.*";

            _dispatcher = dispatcher;
            InEditMode = inEditMode;

            _disk = disk;
            _folderInfo = folderInfo;

            _disk.FileCreated += OnFileCreated;
            _disk.FileRemoved += OnFileRemoved;

            _disk.FolderCreated += OnFolderCreated;
            _disk.FolderRemoved += OnFolderRemoved;

            _files = new FsThreadSafeObservableCollection<FsFileViewModel>(dispatcher);
            _files.CollectionChanged += FilesCollectionChanged;

            _folders = new FsThreadSafeObservableCollection<FsFolderViewModel>(dispatcher);
            _folders.CollectionChanged += FoldersCollectionChanged;

            ReloadFolders();
            ReloadFiles();
        }

        private readonly FsThreadSafeObservableCollection<FsFileViewModel> _files;
        /// <summary>
        /// A thread-safe ObservableCollection of file View Models
        /// </summary>
        public ObservableCollection<FsFileViewModel> Files
        {
            get { return _files; }
        }

        private readonly FsThreadSafeObservableCollection<FsFolderViewModel> _folders;
        /// <summary>
        /// A thread-safe ObservableCollection of subfolder View Models
        /// </summary>
        public ObservableCollection<FsFolderViewModel> Folders
        {
            get { return _folders; }
        }

        // displayed properties

        public override string Name
        {
            get
            {
                return _folderInfo.IsTheSameFolder(_disk.RootFolder) ? "(Root)" : _folderInfo.Name;
            }
            set
            {
                if (_folderInfo.IsTheSameFolder(_disk.RootFolder))
                    throw new ArgumentException("Cannot edit root folder name");

                _disk.RenameFolder(FullName, _folderInfo.Path() + FsDisk.ExternalPathSeparator + value);
            }
        }

        internal string FullName { get { return _folderInfo.FullName; } }

        public string FilesTabTitle
        {
            get { return _files.Count + " files in " + FullName; }
        }

        public string SearchPattern
        {
            get; set;
        }

        private FsRelayCommand _createSubfolderCommand;
        /// <summary>
        /// Create Child Folder command
        /// </summary>
        public ICommand CreateSubfolderCommand
        {
            get
            {
                return _createSubfolderCommand ?? (_createSubfolderCommand = new FsRelayCommand
                (
                    _ => CreateSubfolder()
                ));
            }
        }

        private FsRelayCommand _removeFolderCommand;
        /// <summary>
        /// Remove This Folder command
        /// </summary>
        public ICommand RemoveFolderCommand
        {
            get
            {
                return _removeFolderCommand ?? (_removeFolderCommand = new FsRelayCommand
                (
                    _ => _disk.RemoveFolder(FullName),
                    _ => (!_folderInfo.IsTheSameFolder(_disk.RootFolder))
                ));
            }
        }

        private FsRelayCommand _createFileCommand;
        /// <summary>
        /// Create File command
        /// </summary>
        public ICommand CreateFileCommand
        {
            get
            {
                return _createFileCommand ?? (_createFileCommand = new FsRelayCommand
                (
                    _ => CreateFile()
                ));
            }
        }

        private FsRelayCommand _searchCommand;
        /// <summary>
        /// Search For Files command
        /// </summary>
        public ICommand SearchCommand
        {
            get
            {
                return _searchCommand ?? (_searchCommand = new FsRelayCommand
                (
                    _ =>
                    {
                        _inSearchMode = true;
                        FindFiles();
                    }
                ));
            }
        }

        private FsRelayCommand _searchOffCommand;
        /// <summary>
        /// Switch Search Mode Off command
        /// </summary>
        public ICommand SearchOffCommand
        {
            get
            {
                return _searchOffCommand ?? (_searchOffCommand = new FsRelayCommand
                (
                    _ =>
                    {
                        if (_inSearchMode)
                            ReloadFiles();
                        _inSearchMode = false;
                    },
                    _ => _inSearchMode
                ));
            }
        }

        private FsRelayCommand _importFSCommand;
        /// <summary>
        /// Import Another VirtualFS File command
        /// </summary>
        public ICommand ImportFSCommand
        {
            get
            {
                return _importFSCommand ?? (_importFSCommand = new FsRelayCommand
                (
                    fileName => _disk.CopyFromAsync((string)fileName, @"\", FullName )
                ));
            }
        }

        private FsRelayCommand _importFolderCommand;
        /// <summary>
        /// Import Physical Folder command
        /// </summary>
        public ICommand ImportFolderCommand
        {
            get
            {
                return _importFolderCommand ?? (_importFolderCommand = new FsRelayCommand
                (
                    folderName => _disk.ImportAsync((string)folderName, FullName)
                ));
            }
        }

        private FsRelayCommand _exportFolderCommand;
        /// <summary>
        /// Export Current Folder to a Physical Folder command
        /// </summary>
        public ICommand ExportFolderCommand
        {
            get
            {
                return _exportFolderCommand ?? (_exportFolderCommand = new FsRelayCommand
                (
                    folderName => _disk.ExportAsync((string)folderName, FullName)
                ));
            }
        }

        // how many times we're going to try to invent a unique new name
        private const int MaxNewNameTries = 1000;

        /// <summary>
        /// Creates a child folder
        /// </summary>
        private void CreateSubfolder()
        {
            // getting a new unique name

            const string baseName = "New Folder";
            string newName = baseName;

            // a list of existing folders starting with "New Folder"
            var existingFolders = 
            (
                from f in _disk.GetSubFolders(FullName) where f.Name.StartsWith(baseName) select f.Name
            ).ToArray();

            for (var i = 1; i <= MaxNewNameTries; i++)
            {
                newName = baseName + i;
                if (!existingFolders.Contains(newName))
                    break;
            }

            _isCreatingChildFolder = true;
            try
            {
                _disk.CreateFolder(FullName + FsDisk.ExternalPathSeparator + newName);
            }
            finally
            {
                _isCreatingChildFolder = false;
            }
        }

        /// <summary>
        /// Creates a file
        /// </summary>
        private void CreateFile()
        {
            // getting a new unique name

            const string baseName = "New File";
            string newName = baseName;

            // a list of existing folders starting with "New Folder"
            var existingFiles =
            (
                from f in _disk.GetFiles(FullName) where f.Name.StartsWith(baseName) select f.Name
            ).ToArray();

            for (var i = 1; i <= MaxNewNameTries; i++)
            {
                newName = baseName + i;
                if (!existingFiles.Contains(newName))
                    break;
            }

            _isCreatingFile = true;
            try
            {
                _disk.CreateFile(FullName + FsDisk.ExternalPathSeparator + newName);
            }
            finally
            {
                _isCreatingFile = false;
            }
        }

        /// <summary>
        /// Disposes file View Models on deletion
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FilesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            // Disposing unused file ViewModels
            if (e.OldItems != null && e.OldItems.Count != 0)
            {
                foreach (FsFileViewModel vm in e.OldItems)
                    vm.Dispose();
            }

            OnPropertyChanged("FilesTabTitle");
        }

        /// <summary>
        /// Disposes child folder View Models on deletion
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void FoldersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            // Disposing unused file ViewModels
            if (e.OldItems != null && e.OldItems.Count != 0)
            {
                foreach (FsFolderViewModel vm in e.OldItems)
                    vm.Dispose();
            }
        }

        /// <summary>
        /// File creation handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnFileCreated(object sender, FsFileChangedEventArgs e)
        {
            if (!_folderInfo.ContainsFile(e.FileInfo))
                return;

            _files.Add(new FsFileViewModel(_disk, e.FileInfo, _isCreatingFile));
        }

        /// <summary>
        /// File removal handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnFileRemoved(object sender, FsFileRemovedEventArgs e)
        {
            var fileViewModel = _files.FirstOrDefault(f => f.FullName == e.FileName);

            if(fileViewModel != null)
                _files.Remove(fileViewModel);
        }

        /// <summary>
        /// Folder creation handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnFolderCreated(object sender, FsFolderChangedEventArgs e)
        {
            if (!_folderInfo.ContainsSubfolder(e.FolderInfo))
                return;

            _folders.Add(new FsFolderViewModel(_dispatcher, _disk, e.FolderInfo, _isCreatingChildFolder));
        }

        /// <summary>
        /// Folder removal handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnFolderRemoved(object sender, FsFolderRemovedEventArgs e)
        {
            if (!_folderInfo.ContainsSubfolder(e.FolderName))
                return;

            var folderViewModel = _folders.Single(f => f.FullName == e.FolderName);
            _folders.Remove(folderViewModel);
        }

        /// <summary>
        /// Loads files in current folder
        /// </summary>
        private void ReloadFiles()
        {
            _files.Clear();

            foreach (var fileInfo in _disk.GetFiles(FullName))
            {
                _files.Add(new FsFileViewModel(_disk, fileInfo));
            }
        }

        /// <summary>
        /// Searches for files in current folder and subfolders
        /// </summary>
        private void FindFiles()
        {
            _files.Clear();

            foreach (var fileInfo in _disk.FindFiles(FullName, SearchPattern))
            {
                _files.Add(new FsFileViewModel(_disk, fileInfo, false, true));
            }
        }

        /// <summary>
        /// Reloads child folders
        /// </summary>
        private void ReloadFolders()
        {
            _folders.Clear();

            foreach (var folderInfo in _disk.GetSubFolders(FullName))
            {
                _folders.Add(new FsFolderViewModel(_dispatcher, _disk, folderInfo));
            }
        }

        protected override void OnDispose()
        {
            // Disposing file ViewModels
            foreach(var vm in _files)
                vm.Dispose();
            _files.CollectionChanged -= FilesCollectionChanged;

            // Disposing folder ViewModels
            foreach (var vm in _folders)
                vm.Dispose();
            _folders.CollectionChanged -= FoldersCollectionChanged;

            _disk.FileCreated -= OnFileCreated;
            _disk.FileRemoved -= OnFileRemoved;

            _disk.FolderCreated -= OnFolderCreated;
            _disk.FolderRemoved -= OnFolderRemoved;
        }
    }
}
